<?php
/*
 * Copyright (c) 2006-2009 Ben Blanks <ben.a.blanks@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * Spectre Template Engine 3.0.6
 * 
 * Version 3 of the Template Engine is a complete rewrite of the
 * template engine. This version increases performance as well as implements
 * features that the previous versions did not include.
 *
 * Block support is now included as standard functionality for switching
 * between sections of the page. 
 * 
 * Repeating Rows is now included as standard functionality to give the
 * ability to repeat a specific block of code over and over again by sending
 * an array of values to replace it with.
 * 
 * 
 * 
 ****************************************************************************
 * Examples of usage
 ****************************************************************************
 * -- START TEMPLATE EXAMPLE --
 * <html>
 * <body>
 * 
 * {block.start( BLOCK1 )}
 *   {block.start( BLOCK2 )}
 * 
 *   {block.end( BLOCK2 )}
 * {block.end( BLOCK1 )}
 * 
 * <table>
 * {row.start( row1 )}
 *   <tr>
 *     {row.start( row2 )}
 *     <td>{ field_name }</td>
 *     {row.end( row2 )}
 *   </tr>
 * {row.end( row1 )}
 * </table>
 * 
 * </body>
 * </html>
 * -- END TEMPLATE EXAMPLE -- 
 * 
 * $oSpectre = new Spectre();
 * 
 * $aTags = array(
 * 	 'tag1' => '1',
 *   'tag2' => '1',
 *   'tag3' => '1',
 * );
 * 
 * // Simple Single Level rowset
 * $aRows['ident'][]['tags'] = array(
 *   'tag' => value,
 * );
 * // Advanced nested rowset
 * // as you can see below you can loop through and create them 
 * // without a problem within a foreach lop
 * 
 * $aRows['ident'][0]['rowset']['ident2'][]['tags'] = array('tag' => value);
 * $aRows['ident'][0]['tags'] = array('tag' => value);
 * $aRows['ident'][1]['rowset']['ident2'][]['tags'] = array('tag' => value);
 * $aRows['ident'][1]['tags'] = array('tag' => value);
 * 
 * $oSpectre->setTemplate( 'template_ident' , 'path/to/template/file.ext' );
 * $oSpectre->setTags( $aTags , 'template_ident' );
 * $oSpectre->setBlock( 'BLOCK1' , 'show' , 'template_ident' );
 * $oSpectre->setBlock( 'BLOCK2' , 'hide' , 'template_ident' );
 * $oSpectre->setRows( $aRows , 'template_ident' );
 * echo $oSpectre->parseTemplate( 'template_ident' );
 * 
 * 
 * 
 ****************************************************************************
 * History
 **************************************************************************** 
 * 3.0.6 - 2010-01-25
 *     JS Minimisation and Optimisation techniques (Experimental - USE at your own risk)
 *     CSS Minimisation and Optimisation techniques (Experimental - USE at your own risk)
 * 
 * 3.0.5 - 2010-01-23
 *     Performance bugfix, when loading multiples of the same file.
 *     
 * 3.0.4 - 2010-01-22
 *     bugfix that fixed an issue with blocks not parsing the rows within 
 *     them grr..
 *     
 * 3.0.3 - 2009-12-13
 *     bugfix that fixed an issue with blocks being set within rows.
 *     The blocks were not getting the tags being set within the rows.
 *     This has been fixed by setting the blocks on each row as it 
 *     parses the row content and before it actually replaces the tags 
 *     in the row.
 * 
 * 
 * 3.0.2 - 2009-10-21
 *     reworking of the way the tags blocks and rows are parsed so 
 *     that it orders properly no matter which order the commands are 
 *     set.
 * 
 * 3.0.1 - 2009-02-27
 *     Ability to have nested blocks, rows within each other.
 * 
 * 3.0.0 - 2008-11-07
 *     Rewritten from scratch for performance + code managability.
 *     Many bug fixes as well as numerous enhancements.
 *     Introduction of Rows and Blocks. 
 *     IS BACKWARDS COMPATABLE.
 *
 *
 ****************************************************************************
 * TO-DOs
 ****************************************************************************
 * 
 * Restructure class so that it is easier to read and is using a better 
 * variable naming convention. eg. 
 * 	 $aArray = array(); 
 *   $sString = ''; 
 *   $iNumber = 1;
 *   $oObject = new stdClass;
 * 
 ****************************************************************************
 * @package Spectre
 * @author Ben Blanks <ben.a.blanks@gmail.com>
 * @version 3.0.6
 */
class Spectre {
	/**
	 * Sets the status of the engines cleanup functionality, if true then the
	 * parser will clean all the unreplaced tags as a part of its parsing.
	 * 
	 * @var boolean
	 */
	public $_production          = false;
	
	/**
	 * Regular expression for getting the tag content.
	 * @var string
	 */
	private $_regex_required     = "[^<>]+";
	/**
	 * Regular expression snippet for the tag start
	 * @var string
	 */
	public $_tag_start           = "{\s*";
	/**
	 * Tag Seperator is used to assign different groupings to the tag. useful 
	 * for saying i want multiple options for this tag.
	 * @var string
	 */
	public $_tag_seperator       = "\s*:\s*";
	/**
	 * Regular expression snippet for the tag finish
	 * @var string
	 */
	public $_tag_end             = "\s*}";
	/**
	 * Assigned to the start of the looping snippet.
	 * @var string
	 */
	public $_row_start           = "row.start\(\s*({STRING})\s*\)";
	/**
	 * Assigned to the end of the looping snippet
	 * @var string
	 */
	public $_row_end             = "row.end\(\s*{STRING}\s*\)";
	/**
	 * This is the format that will be used to place the holding 
	 * tag for the looping snippets after bring read into memory. 
	 * @var string
	 */
	public $_row_placeholder     = "row\(\s*{STRING}\s*\)";
	/**
	 * Start pattern of the block snippet which allows blocks to be turned on and off
	 * from php before publishing.
	 * @var string
	 */
	public $_block_start         = "block.start\(\s*({STRING})\s*\)";
	/**
	 * End pattern of the block snippet which allows blocks to be turned on and off
	 * from php before publishing.
	 * @var string
	 */
	public $_block_end           = "block.end\(\s*{STRING}\s*\)";
	/**
	 * Pattern of the block placeholder which allows blocks to be turned on and off
	 * from php before publishing.
	 * @var string
	 */
	public $_block_placeholder   = "block\(\s*{STRING}\s*\)";
	/**
	 * List of tags being held in memory for a specific template.
	 * @var array
	 */
	private $vars;
	/**
	 * Holds the content.
	 * @var array
	 */
	public $_content              = array();
	public $_blocks               = array();
	public $_rows                 = array();
	/**
	 * Holds the cached templates to improve performance of the template
	 * engine by allowing the data to be read from memory if it already 
	 * had been read previously from file. 
	 * @var array
	 */
	private $_cache               = array();
	private $_cache_dir           = 'spectre-cache/';
	
	private $_optimise_loader     = 'spectre-optimiser.php';
	
	private $_site_path           = '';
	
	function __construct() {
		// Replaces the reg_req with the correct variable
		$this->_row_start         = str_replace( '{STRING}', $this->_regex_required , $this->_row_start );
		$this->_row_end           = str_replace( '{STRING}', $this->_regex_required , $this->_row_end );
		$this->_block_start       = str_replace( '{STRING}', $this->_regex_required , $this->_block_start );
		$this->_block_end         = str_replace( '{STRING}', $this->_regex_required , $this->_block_end );
		
		// used to dynamically get the different regex values
		$this->vars = get_class_vars( get_class( $this ) );
		$this->_cache_dir   = dirname( __FILE__ ) . '/'.$this->_cache_dir;
		$this->_site_path   = './';
	}
	/**
	 * Very simple wrapper function for a key generator based on the
	 * string name. (lets just say that this is here for future 
	 * expandability of the key generator)
	 * 
	 * @param string $string the key data to be uniquified (even a word?)
	 * @return string
	 */
	private function getKey( $string = false ) {
		return md5( $string );
	}
	
	/**
	 * Starting of a regular expression to string converter
	 * 
	 * @param string $string the regular exression to be converted.
	 * @return string
	 */
	public function reg2String( $string = false ) {
		if ( $string !== false ) {
			$string = str_replace( "\s*" , '' , $string );
			$string = str_replace( "\s+" , ' ' , $string );
			$string = str_replace( "\\" , '' , $string );
		}
		return $string;
	}

	/**
	 * Constructs the template from file or searches for it in memory 
	 * and parses all the tags, rows, blocks into their respective areas
	 * for the set and parse functions to be able to do their jobs.
	 * 
	 * @param string $ref The template identifier to be used for accessing this template.
	 * @param multi $filepath Either a filepath or string to be used for the template.
	 * @return boolean
	 */
	public function setTemplate( $ref = false , $filepath = false ) {
		if ( $ref !== false && $filepath !== false ) {
			$file_key = $this->getKey( $filepath );
			if ( !array_key_exists( $file_key , $this->_cache ) ) {
				
			// clear the refernce that will conflict with another file.
				$existing_ref = array_search( $ref , $this->_cache );
				if( $existing_ref !== false ) {
					unset( $this->_cache[$existing_ref] );
				}
				
				$file_true = ( file_exists( $filepath ) && !is_dir( $filepath ) );
				if ( $file_true ) {
					$html = @ file_get_contents( $filepath );
				// Gathering the rows and blocks throughout the content block
				// this is a recursive function which created a multi-dimensional
				// array
					list( $html , $rows )   = $this->gather( 'row' , $html );
					list( $html , $blocks ) = $this->gather( 'block' , $html );
					$this->_content[$ref] = array(
						'content'      => $html,
						'path'         => $filepath,
						'replacements' => array(),
						'blocks'       => $blocks,
						'rows'         => $rows,
					);
				}
				else {
					list( $filepath , $rows )   = $this->gather( 'row' , $filepath );
					list( $filepath , $blocks ) = $this->gather( 'block' , $filepath );
						
					$this->_content[$ref] = array(
						'content'      => $filepath,
						'path'         => $filepath,
						'replacements' => array(),
						'blocks'       => $blocks,
						'rows'         => $rows,
					);
				}
				$this->_cache[$file_key] = $ref;
			}
			else {
				$this->_content[$ref]['replacements'] = array();
				unset( $this->_blocks[$ref] , $this->_rows[$ref] , $this->vars[$ref] );
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Runs through the final parsing of a content section that will be
	 * returned to the script that ran it.
	 * 
	 * @param string $ref The reference string to be used to call the template.
	 * @return string on success and false on failure.
	 */
	public function parseTemplate( $ref = false , $optimised = false ) {
		if ( $ref !== false && array_key_exists( $ref , $this->_content ) ) {
			$content = $this->_content[$ref]['content'];
			$content = $this->parseContent( $ref , $content , 'blocks' ); // To initalise any blocks that contain rows and/or tags
			$content = $this->parseContent( $ref , $content , 'tags' );
			$content = $this->parseContent( $ref , $content , 'rows' );
			
			$content = $this->hideBlocks( $content );
			$content = trim( $content );
			if ( $this->_production == true ) {
				$content = $this->replaceStrays( $content );
			}
			if( $optimised ) {
				$content = $this->parseOptimised( $content );
			}
			return $content;
		}
		return false;
	}
	
	public function optimiserFile( $sPath ) {
		$this->_optimise_loader = $sPath;
	}
	
	public function sitePath( $sPath ) {
		$this->_site_path = $sPath;
	}
	
	/**
	 * A new parsing method that allows for optimisation of the html
	 * to be able to load faster in browsers. It minimises css and javascript
	 * as well as groups them into 1 call of each type.
	 * 
	 * Helper Method
	 * 
	 * @since 3.0.6
	 * 
	 * @param string $content the html you wish to optimise.
	 * @return string
	 */
	private function parseOptimised( $content ) {
		$content = $this->parseOptimised_CSS( $content );
		$content = $this->parseOptimised_JS( $content );
		return $content;
	}
	
	/**
	 * Helper method that combines the javascript files and makes a cache file
	 * based on the options in the template.
	 * 
	 * @since 3.0.6
	 * 
	 */
	private function parseOptimised_JS( $content ) {
		if( $content != false ) {
			$regex_groups     = "/<script (.*)><\/script>/msiU";
			preg_match_all( $regex_groups, $content, $groups, PREG_SET_ORDER );
			$modified = 0;
			$js_files = array();
			$replaced = false;
			foreach( $groups as $group ) {
				$attrs_string = $group[1];
				preg_match_all( "/.*((src|type)=\"(.*)\")+.*/msiU" , $attrs_string , $attrs );
				
				// get the attributes
				$keys  = isset($attrs[2])?$attrs[2]:array();
				$values = isset($attrs[3])?$attrs[3]:array();
				// reset attrs
				$attrs = array();
				foreach( $keys as $id => $key ) {
					$attrs[$key] = $values[$id];
				}
				// language is now deprecated really so i wont be checking for it.
				
				if( strtolower( $attrs['type'] ) == 'text/javascript' ) {
					if( isset( $attrs['src'] ) && file_exists( $attrs['src'] ) ) {
						$js_files[] = $attrs['src'];
						$mtime = filemtime( $attrs['src'] );
						$modified     = ($modified < $mtime ) ? $mtime : $modified;
					}
				}
				if( !$replaced ) {
					$content = str_replace( $group[0] , '{spectre_js}' , $content );
				}
				else {
					$content = str_replace( $group[0] , '' , $content );
				}
				$replaced = true;
			}
			$cache_name = 'js_'.md5(print_r( $js_files , true ));
			$cache_path = $this->_cache_dir . $cache_name;
			
			
			$update_cache = false;
			if( !file_exists( $cache_path ) || filemtime( $cache_path ) != $modified ) {
				$update_cache = true;
			}
			
			if( $update_cache && is_array( $js_files ) && count( $js_files ) > 0 ) {
				$combined_src = '';
				foreach( $js_files as $js_file ) {
					if( file_exists( $this->_site_path.$js_file ) ) {
						$combined_src .= file_get_contents( $this->_site_path.$js_file );
					}
				}
				file_put_contents( $cache_path , $combined_src );
				touch( $cache_path , $modified );
			}
			$content = str_replace( '{spectre_js}' , '<script type="text/javascript" src="'.$this->_optimise_loader.'?f='.$cache_name.'"></script>' , $content );
		}
		return $content;
	}
	/**
	 * Helper method that combines and minified css files for faster
	 * and smaller requests on your pages.
	 * 
	 * @since 3.0.6
	 * 
	 * @param string $content the html you wish to optimise.
	 * @return string
	 */
	private function parseOptimised_CSS( $content ) {
		if( $content != false ) {
			
			// CSS
			$regex_groups     = "/<link (.*) \/>/msiU";
			//$regex_attr_href  = "/.*(href)=\"([^\"]+)\"+.*/";
			//$regex_attr_type  = "/.*(type)=\"([^\"]+)\"+.*/";
			//$regex_attr_media = "/.*(media)=\"([^\"]+)\"+.*/";
			
			$stylesheets = array();
			preg_match_all( $regex_groups, $content, $groups, PREG_SET_ORDER );
			$replaced = array();
			foreach( $groups as $group ) {
				$style_attrs = $group[1];
				preg_match_all( "/.*((href|rel|type|media)=\"(.*)\")+.*/msiU" , $style_attrs , $attrs );
				
				$keys  = isset($attrs[2])?$attrs[2]:array();
				$values = isset($attrs[3])?$attrs[3]:array();
				$attrs = array();
				foreach( $keys as $id => $key ) {
					$attrs[$key] = $values[$id];
				}
				
				if( 
					isset( $attrs['type'] ) && 
					isset( $attrs['href'] ) && 
					isset( $attrs['rel'] ) && 
					strtolower( $attrs['rel'] ) == 'stylesheet' && 
					strtolower( $attrs['type'] ) == 'text/css' 
				) {
					$media = 'screen';
					if ( isset( $attrs['media'] ) ) {
						$media = strtolower($attrs['media'] );
					}
					$stylesheets[$media][] = $attrs['href'];

					// place a placeholder for new css
					if( !isset( $replaced[$media] ) ) {
						$content = str_replace( $group[0] , '{spectre_css_'.$media.'}' , $content );
						$replaced[$media] = true;
					}
					else {
						$content = str_replace( $group[0] , '' , $content );
					}
				}
			}
			
			foreach( $stylesheets as $media => $files ) {
				$cache_name = 'css_'.md5($media . print_r( $files , true ));
				$cache_path = $this->_cache_dir . $cache_name;
				
				// Get the latest modified time.
				$iTimestamp = 0;
				foreach( $files as $file ) {
					$iTS = filemtime( $file );
					$iTimestamp = ($iTS > $iTimestamp) ? $iTS : $iTimestamp;
				}
				if( !file_exists( $cache_path ) || filemtime( $cache_path ) != $iTimestamp ) {
					$css_content = '';
					foreach( $files as $file ) {
						if( file_exists( $this->_site_path.$file ) ) {
							$css_content .= file_get_contents($this->_site_path.$file);
						}
					}
					$css_content = preg_replace( '/@CHARSET ".*"\;/' , '' , $css_content );
					$css_content = preg_replace( "/\/\*(.*)\*\//msiU" , '' , $css_content );
					$css_content = preg_replace( "/[\r\n]/msi" , '' , $css_content );
					$css_content = preg_replace( "/;[\t\s\r\n]+/msi" , ';' , $css_content );
					$css_content = preg_replace( "/{[\t\s\r\n]+/msi" , '{' , $css_content );
					$css_content = preg_replace( "/}[\t\s\r\n]+/msi" , '}' , $css_content );
					$css_content = preg_replace( "/:[\t\s\r\n]+/msi" , ':' , $css_content );
					$css_content = preg_replace( "/[\t\s\r\n]+{/msi" , '{' , $css_content );
					
					file_put_contents( $cache_path , $css_content );
					// set cache file to the latest modified timestamp for comparison
					touch( $cache_path , $iTimestamp );
				}
				
				$content = str_replace( '{spectre_css_'.$media.'}' , 
					'<link rel="stylesheet" type="text/css" media="'.$media.'" href="'.$this->_optimise_loader.'?f='.$cache_name.'" />',
					$content );
				
			}
			
		}
		return $content;
	}

	public function loadCacheFile( $file ) {
		$aReturn[] = file_get_contents( $this->_cache_dir . $file );
		$aReturn[] = filemtime($this->_cache_dir . $file);
		return $aReturn;
	}
	
	/**
	 * Parses the tags inside the template specified and returns them
	 * for use.
	 * 
	 * @param string $ref unique identifier used for getting a template set.
	 * @param $content The content to be used
	 * @return unknown_type
	 */
	private function parseContent( $ref = false , $content = '' , $type = 'tags' )  {
		if ( $ref !== false && $content != false ) {
			$replacements = array();
			if( $type == 'blocks' ) {
				if ( array_key_exists( $ref , $this->_blocks ) ) {
					$replacements = $this->_blocks[$ref]['replacements'];
				}	
			}
			else if( $type == 'rows' ) {
				if ( array_key_exists( $ref , $this->_rows ) ) {
					$replacements = $this->_rows[$ref]['replacements'];
				}	
			}
			else {
				if ( array_key_exists( $ref , $this->_content ) ) {
					$replacements = $this->_content[$ref]['replacements'];
				}
			}
			if ( count( $replacements ) ) {
				foreach ( $replacements as $replacement ) {
					$content = $this->setContent( $replacement['tag'] , $replacement['replace'] , $content );
				}
			}
		}
		return $content;
	}
	/**
	 * @todo Must do this as its required
	 * 
	 * @param unknown_type $ref
	 * @return unknown_type
	 */
	private function getCacheKey( $ref = false )  {
		$cache_key = '';
		if ( $ref !== false ) {
			$replacements = array();
			$replacements[] = array_search( $ref , $this->_cache );
			if ( array_key_exists( $ref , $this->_blocks ) ) {
				$replacements[] = $this->_blocks[$ref]['replacements'];
			}	
			if ( array_key_exists( $ref , $this->_rows ) ) {
				$replacements[] = $this->_rows[$ref]['replacements'];
			}	
			if ( array_key_exists( $ref , $this->_content ) ) {
				$replacements[] = $this->_content[$ref]['replacements'];
			}
			$cache_key = md5( print_r( $replacements , true ) );
		}
		return $cache_key;
	}

	/**
	 * Single Tag Replace
	 * --------------------------------------------------------------------------
	 * Function will have multiple "Modes" in a way that if you insert a tag
	 * that exists in the class content library it will place it into its
	 * relevant replacement area for parsing on parseTemplate being called.
	 *
	 * If you enter straight content that is not a tag in the content
	 * library then it will return the content with the tags replaced
	 * 
	 * @param $tag
	 * @param $replace
	 * @param $content
	 * @return unknown_type
	 */
	private function setContent( $tag = false , $replace = '' , $content = false , $type = 'tag' ) {
		$tag = str_replace( "|" , "\|" , $tag );
		if ( $tag !== false && $content !== false ) {
			if ( array_key_exists( $content , $this->_content ) ) {
				//$content = $this->_content[$content];
			if( $type == 'block' ) {
					$this->_blocks[$content]['replacements'][] = array(
						'tag'     => $tag ,
						'replace' => $replace ,
					);
				}
				else if( $type == 'row' ) {
					$this->_rows[$content]['replacements'][] = array(
						'tag'     => $tag ,
						'replace' => $replace ,
					);
				}
				else {
					$this->_content[$content]['replacements'][] = array(
						'tag'     => $tag ,
						'replace' => $replace ,
					);
				}
			}
			else {
				$regex = "/".$this->_tag_start.$tag.$this->_tag_end."/";
				$content = preg_replace( $regex , $replace , $content );
			}
			return $content;
		}
		return false;
	}

	/**
	 * Multitag Replace
	 * --------------------------------------------------------------------------
	 * Function will have multiple "Modes" in a way that if you insert a content
	 * value that exists in the class content library it will place it into its
	 * relevant replacement area for parsing on parseTemplate being called.
	 *
	 * If you enter straight content that has not been placed into the content
	 * library then it will return the content with the tags replaced
	 *
	 * $replacements must be in an associative array in the following format
	 * array(
	 * 	array( 'tag' => '1'   , 'replace' => 'one'),
	 * 	array( 'tag' => '2'   , 'replace' => 'two'),
	 * 	array( 'tag' => '3' , 'replace'   => 'three'),
	 * )
	 *
	 * !!!! REFERS BACK TO setContent for its tag replacements
	 * 
	 * @param $replacements
	 * @param $content
	 * @param $prefix
	 * @param $postfix
	 * @return unknown_type
	 */
	public function setTags( $replacements = array() , $content = false , $prefix = '' , $postfix = '' ) {
		if ( is_array( $replacements ) && count( $replacements ) > 0 ) {
			foreach ( $replacements as $tag => $replacement ) {
				$content = $this->setContent( $prefix . $tag . $postfix , $replacement , $content );
			}
		}
		return $content;
	}

	public function arr2TagArr( $array = false ) {
		$return = array();
		if ( is_array( $array ) ) {
			foreach ( $array as $k => $v ) {
				$return[] = array( 'tag' => $k , 'replace' => $v );
			}
		}
		return $return;
	}
	
	/**
	 * What this does is that it loops through a 2 dimensional array and then does a
	 * tag replace on a specific block of html / content within loadedDivided zone function with password protection content.
	 *
	 * This allow you to easily do a tabular format within the same html / content
	 * document.
	 * 
	 * @todo Repair the functions so that the naming convention is better and more descriptive.
	 *
	 * @param $rows
	 * @param $ref
	 * @param $row_array
	 * @return unknown_type
	 */
	public function setRows_process( $rows = array() , $ref = false , $row_array = false ) {
		$row_content = array();
		if ( $row_array === false ) {
			$row_array = $this->_content[$ref]['rows'];
		}
		else {
			$row_array = $row_array['elements'];
		}
		if ( is_array( $rows ) && $ref !== false ) {
			foreach ( $rows as $k => $v ) {
				if ( !isset( $row_content[$k] ) ) {
					$row_content[$k] = '';
				}
				if ( isset( $row_array[$k] ) && isset( $v ) && is_array( $v ) && count( $v ) > 0 ) {
					
					$tmp_content = $row_array[$k]['content'];
					// Placed here for the blocks that are set within rows so they can get the row tags as well.
					$tmp_content = $this->parseContent( $ref , $tmp_content , 'blocks' );
					foreach ( $v as $k2 => $v2 ) {
						if ( isset( $row_array[$k] ) ) {
							//$tags = $this->arr2TagArr( $v2['tags'] );
							// bugfix: bb 2009-02-09 : no checking if the 'tags' assoc value existed.
							$tags = array();
							if ( isset( $v2['tags'] ) ) { 
								$tags = $v2['tags']; 
							}
							
							$row_content[$k] .= $this->setTags( $tags , $tmp_content );
							
							if ( isset( $v2['rowset'] ) && is_array( $v2['rowset'] ) && count( $v2['rowset'] ) > 0 ) { 
								$row_data = $this->setRows_process( $v2['rowset'] , $ref , $row_array[$k] );
								foreach ( $row_data as $r => $d ) {
									$row_content[$k] = $this->setContent( '\s*row\(\s*'.$r.'\s*\)\s*' , $d , $row_content[$k] );
								}
							}
						}
					}
				}
				else {
					break;
				}
			}
		}
		return $row_content;
	}
	/*
	 * @todo Comment this function as well as rewrite it a little so that it functions with both strings and content area's
	 * 
	 * Example: $aRows['rowname'][]['tags'] = array();
	 * Example: $aRows['rowname'][rownum]['rowset']['rowname'][]['tags'] = array();
	 * 
	 */
	public function setRows( $rows = array() , $ref = false ) {
		$content = $this->setRows_process( $rows , $ref );
		if ( is_array( $content ) ) {
			foreach ( $content as $k => $v ) {
				$this->setContent( '\s*row\(\s*'.$k.'\s*\)\s*' , $v , $ref , 'row' );
			}
		}
	}

	/**
	 * Gets a tag or multiple tags depending on the flag assigned to the tag parameter.
	 * If you assign anything but a string to the tag parameter it will treat it as saying
	 * get all tags on the page with the prefix or all tags on the page in general.
	 *
	 * Note: ":" represents the tag seperator.
	 * usage: 
	 * getTag( $content , true ); // Gets all tags in the content
	 * getTag( $content , true ); // Gets all tags in the content
	 * getTag( $content , 1 );    // Gets all tags in the content
	 * getTag( $content , false ) // Gets all tags in the content 
	 * 
	 * usage: getTag( $content , false , 'bunny' );              // will get all tags on the page with "bunny : [tagname]"
	 * usage: getTag( $content , false , false , 'bunny' );      // will get all tags on the page with "[tagname] : bunny"
	 * usage: getTag( $content , 'who_is' , 'bunny' , 'blue' );  // will get all tags on the page with "whois : bunny : blue"
	 * usage: getTag( $content , 'code' , 'bunny' );             // will get all tags on the page with "code : blue"
	 *
	 * || Fix the below comment syntax later
	 * @param $content
	 * @param $tag
	 * @param $prefix
	 * @param $postfix
	 * @return array 2 dimensional array with the second dimension being associative with the keys full, tag, prefix and postfix
	 */
	public function getTag( $content = false , $tag = false , $prefix = '' , $postfix = '' ) {
		// checks the tag and constructs the regular expression tag required section;
		if ( !is_string( $tag ) ) { $tag = $this->_regex_required; } // Assigns Any Tagname
		if ( $prefix !== false && $prefix !== '' ) { $prefix = '('.$prefix.')'.$this->_tag_seperator; } // Assigns prefix
		if ( $postfix !== false && $postfix !== '' ) { $postfix = $this->_tag_seperator.'('.$postfix.')'; } // Assigns postfix
		$regex = "/".$this->_tag_start.$prefix.'('.$tag.')'.$postfix.$this->_tag_end."/U"; // Regex Construction
		@ preg_match_all( $regex , $content , $array , PREG_SET_ORDER ); // Gets all Matches
		$final = array();
		foreach ( $array as $k => $v ) { // Checks if all the paramaters for this function will be returned in the array
			$full    = '';
			$tag     = '';
			$prefix  = '';
			$postfix = '';
			@ list( $full , $tag , $prefix , $postfix ) = $v;
			$final[$k]['full'] = $full;
			$final[$k]['tag'] = $tag;
			$final[$k]['prefix'] = $prefix;
			$final[$k]['postfix'] = $postfix;
		}
		return $final;
	}

	/*
	 * setBlock allows for the turning on and off of various blocks. even blocks within blocks
	 * as long as the block is activated in the correct sequence. Meaning that a child block can 
	 * only be activated after its parent block has been activated otherwise the child block will 
	 * not be seen. This is to ensure that you are turning on what you wish to be turned on.
	 * 
	 */
	
	public function setBlocks( $blocks , $ref = false ) {
		if( $ref !== false && is_array( $blocks ) ) {
			foreach( $blocks as $name => $status ) {
				$this->setBlock( $name , $status , 'template' );
			}
		}
	}
	
	/**
	 * $block['refernce']['status'] = 'show';
	 * $block['refernce2']['status'] = 'hide';
	 * $block['refernce']['children']['reference3']['status'] = 'show';

	 * 
	 * @param $block
	 * @param $status the status 'hide,show' is ignored if an array is set in the block
	 * @param $ref Is the reference to the content itsm you wish to assign thsi request to.
	 * @param $block_array allows you to assign an array and set your own block structure up.
	 * @return unknown_type
	 */
	public function setBlock( $block = false , $status = 'hide' ,$ref = false, $block_array = false ) {
		if( $block_array === false ) {
			$block_array = $this->_content[$ref]['blocks'];
		}
		if ( 
			( ( !is_array( $block ) && $status == 'show' ) || is_array( $block ) ) 
			&& $ref !== false && isset( $this->_content[$ref]['blocks'] ) 
		) {
			if ( is_array( $block ) && count( $block ) > 0 ) {
				foreach ( $block as $k => $v ) {
					if ( isset( $block_array[$k]['content'] ) && isset( $v['status'] ) && $v['status'] == 'show' ) {
						$block_content = $block_array[$k]['content'];
						$this->setContent( '\s*block\(\s*'.$k.'\s*\)\s*' , $block_content , $ref , 'block' );
						if ( isset( $v['children'] ) && is_array( $v['children'] ) && count( $v['children'] ) > 0 ) {
							$this->setBlock( $v['children'] , $status , $ref , $block_array[$k]['elements'] );
						}
					}
				}
			}
			else if ( !is_array( $block ) && $block !== false ) {
				if ( isset( $block_array[$block]['content'] ) ) {
					$block_content = $block_array[$block]['content'];
					$this->setContent( '\s*block\(\s*'.$block.'\s*\)\s*' , $block_content , $ref , 'block' );
				}
			}
		}
		return false;
	}
	
	/**
	 * gather allows you to gather the types of toggle/repeatable content sections. It collects
	 * and returns the new content and the elements in an array so that it can then be placed into
	 * the correct location by the calling function.
	 */
	private function gather( $type = false , $content = false ) {
		$elements = array();
		if ( $type !== false && $content !== false ) {
			$reg_middle_start = str_replace( '{STRING}' , $this->_regex_required , $this->vars['_'.$type.'_start'] );
			$reg_middle_end   = str_replace( '{STRING}' , "\\1" , $this->vars['_'.$type.'_end'] );
			$regex  = $this->_tag_start.$reg_middle_start.$this->_tag_end;
			$regex .= "(.*)";
			$regex .= $this->_tag_start.$reg_middle_end.$this->_tag_end;
			preg_match_all( "/{$regex}/msU" , $content , $array , PREG_SET_ORDER );
			foreach ( $array as $match ) {
				$full = $match[0];
				$name = trim( $match[1] );
				$inner = $match[2];
				$element = array (
					'content' => $inner,
					'elements'  => array()
				);
				$content = str_replace( 
					$full , 
					str_replace(
						'{STRING}' , 
						$name , 
						$this->reg2String( $this->_tag_start ).
						$this->reg2String( $this->vars['_'.$type.'_placeholder'] ).
						$this->reg2String( $this->_tag_end ) 
					) , 
					$content 
				);
				if ( preg_match( "/{$regex}/ms" , $inner ) > 0 ) {
					list( $inner , $element ) = $this->gather( $type , $inner );
					$element = array (
						'content' => $inner,
						'elements'  => $element
					);
				}
				$elements[$name] = $element;
			}
		}
		return array( $content , $elements );
	}
	
	/**
	 * Hides all the blocks in the content (done by default)
	 * @param string $content The actual content or the content template ident.
	 * @return string
	 */
	public function hideBlocks( $content ) {
		$tags = $this->getTag( $content , true );
		foreach ( $tags as $tag ) {
			if ( strpos( $tag['full'] , 'block' ) > -1 ) {
				$content = str_replace( $tag['full'] , '' , $content );
			}
		}
		return $content;
	}
	
	/**
	 * Replaces all stray content with an empty string to ensure that 
	 * no stray tags are seen on the site.
	 * 
	 * @param string $content The content to go through and replace the tags.
	 * @return string the content with tags replaced.
	 */
	public function replaceStrays( $content ) {
		$tags = $this->getTag( $content , true );
		foreach ( $tags as $tag ) {
			$content = str_replace( $tag['full'] , '' , $content );
		}
		return $content;
	}
}